package com.spring.demo9;

import com.spring.demo9.module1.Module1Config;
import com.spring.demo9.module1.Service1;
import com.spring.demo9.module1.Service2;
import com.spring.demo9.module2.Module2Config;
import com.spring.demo9.module2.Service3;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

/**
 * @author vhans
 */
public class ParentFactoryTest {

    /**
     * 父子容器特点
     * 1. 父容器和子容器是相互隔离的，他们内部可以存在名称相同的bean
     * 2. 子容器可以访问父容器中的bean，而父容器不能访问子容器中的bean
     * 3. 调用子容器的getBean方法获取bean的时候，会沿着当前容器开始向上面的容器进行查找，直到找到对应的bean为止
     * 4. 子容器中可以通过任何注入方式注入父容器中的bean，而父容器中是无法注入子容器中的bean，原因是第2点
     */
    @Test
    public void test() {
//        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//        // 会报错，因为module1与module2中的service1重名
//        context.register(Module1Config.class, Module2Config.class);
//        context.refresh();

        // 下面使用父子容器来解决问题
        // 方式1:ApplicationContext
        // 创建父容器，注册好Module1Config并启动容器
        AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext(Module1Config.class);
        // 创建子容器
        AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
        childContext.register(Module2Config.class);
        // 给子容器设置父容器
        childContext.setParent(parentContext);
        // 启动子容器
        childContext.refresh();

        Service3 service3 = childContext.getBean(Service3.class);
        System.out.println(service3.m1());
        System.out.println(service3.m2());
    }

    @Test
    public void test1() {
        // 方式2:BeanFactory
        DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
        AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader1 = new AnnotatedBeanDefinitionReader(parentFactory);
        annotatedBeanDefinitionReader1.register(Service1.class, Service2.class);
        // 注入需要的依赖
        parentFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(parentFactory::addBeanPostProcessor);

        DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
        AnnotatedBeanDefinitionReader annotatedBeanDefinitionReader2 = new AnnotatedBeanDefinitionReader(childFactory);
        annotatedBeanDefinitionReader2.register(com.spring.demo9.module2.Service1.class, Service3.class);
        childFactory.getBeansOfType(BeanPostProcessor.class).values().forEach(childFactory::addBeanPostProcessor);

        // 给子容器设置父容器
        childFactory.setParentBeanFactory(parentFactory);

        Service3 service3 = childFactory.getBean(Service3.class);
        System.out.println(service3.m1());
        System.out.println(service3.m2());
    }

    /**
     * 使用容器的过程中，经常会使用到的一些方法，这些方法通常会在下面的两个接口中：
     * org.springframework.beans.factory.BeanFactory // spring容器的顶层接口，这个接口中的方法是支持容器嵌套结构查找的
     * org.springframework.beans.factory.ListableBeanFactory // 不支持容器嵌套结构查找
     * <p>
     * spring中有个工具类BeanFactoryUtils就是可以支持容器嵌套结构查找
     */
    @Test
    public void test2() {
        DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
        parentFactory.registerBeanDefinition("userName", BeanDefinitionBuilder
                .genericBeanDefinition(String.class)
                .addConstructorArgValue("Vhans")
                .getBeanDefinition());

        DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
        childFactory.registerBeanDefinition("address", BeanDefinitionBuilder
                .genericBeanDefinition(String.class)
                .addConstructorArgValue("上海")
                .getBeanDefinition());

        childFactory.setParentBeanFactory(parentFactory);

        System.out.println("获取userName->" + childFactory.getBean("userName"));

        // getBeanNamesForType方法定义在ListableBeanFactory接口中
        // 获取指定类型的所有bean名称，调用这个方法的时候只会返回当前容器中符合条件的bean，而不会去递归查找其父容器中的bean
        System.out.println(Arrays.asList(childFactory.getBeanNamesForType(String.class)));  // 只有address

        System.out.println("=====================");

        // 利用BeanFactoryUtils层次查找所有符合类型的bean名称
        String[] beanNamesForTypeIncludingAncestors = BeanFactoryUtils
                .beanNamesForTypeIncludingAncestors(childFactory, String.class);
        System.out.println(Arrays.asList(beanNamesForTypeIncludingAncestors));
        // 利用BeanFactoryUtils层次查找所有符合类型的bean名称和内容的Map
        Map<String, String> beansOfTypeIncludingAncestors = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(childFactory, String.class);
        System.out.println(Collections.singletonList(beansOfTypeIncludingAncestors));
    }

}
